<?php
declare (strict_types=1);

namespace app\command;

use app\common\lib\Redis;
use app\repository\common\OauthClients;
use Composer\InstalledVersions;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use stdClass;
use Closure;
use app\service\RpcService\Helper\CompressFilter;
use Hprose\Socket\Server;
use Hprose\Future;
use think\app;
use think\facade\Validate;
use think\facade\Route;
use app\common\lib\MsgQueue;


class RpcServer extends Command
{
    private $mqClient;
    private $clientRep;

    private static $uris;
    protected $output;

    private static $instance = [];

    protected $registerMethods = [];
    protected static $routes = [];
    private static $routeConfig = [];

    public function __construct()
    {
        parent::__construct();
        $this->mqClient = new MsgQueue();
        $this->clientRep = new OauthClients();
        self::$uris = config('hprose.uris');
        self::$routeConfig = config('rpc_methods') ?? [];
    }

    protected function configure()
    {
        $this->setName('RpcServer')->setDescription('RPC服务端');
    }

    protected function execute(Input $input, Output $output)
    {
        $this->output = $output;
        $middleHandler = function ($name, array &$args, stdClass $context, Closure $next) {
            $requestParam = $args[0] ?? '';
            $res = $this->validParams($args[0] ?? '');

            if ($res['status'] != STATUS_CODE_SUCCESS) {
                $this->responseCallBack($name, $res, $requestParam);
                return $this->response($res['status'], $res['msg']);
            }
            /*解析参数主体，且合并进api_key*/
            $requestBody = [0 => array_merge($res['data']['data'], ['api_key' => $res['data']['api_key']])];
            $result = $next($name, $requestBody, $context);

            if (Future\isFuture($result)) {
                $result->then(function ($result) use ($requestParam, $name) {
                    $this->responseCallBack($name, $result, $requestParam);
                });
            } else {
                $this->responseCallBack($name, $result, $requestParam);
            }
            return $result;
        };

        $server = $this->register();
        $server->addInvokeHandler($middleHandler);
        //$server->addFilter(new \app\service\RpcService\Helper\SizeFilter('compress before'));
        $server->addFilter(new CompressFilter());
        //$server->addFilter(new \app\service\RpcService\Helper\SizeFilter('compress after'));
        $this->outputInfo();
        $server->start();;

    }

    /**
     * 校验参数
     * User:端木凌风
     * Date:2023-11-09 01:04
     * @param $params
     * @return object|array|void|mixed
     */
    protected function validParams($params)
    {
        if (!is_string($params)) {
            return funReturn(STATUS_CODE_DATAERROR, '参数不是有效的Json结构');
        }
        try {
            $paramData = json_decode($params, true);
        } catch (\Exception $e) {
            return funReturn(STATUS_CODE_DATAERROR, '参数不是有效的Json结构');
        }
        if (!is_array($paramData)) {
            return funReturn(STATUS_CODE_DATAERROR, '参数不是有效的Json结构');
        }

        $rule = [
            'header' => 'require|array',
            'body' => "array"
        ];
        $message = [
            'header.require' => '缺少header参数',
            'header.array' => 'header参数格式错误',
            'body.require' => 'body参数格式错误',
        ];
        $validate = Validate::message($message)->rule($rule);
        if (!$validate->check($paramData)) {
            return funReturn(STATUS_CODE_DATAERROR, $validate->getError());
        }

        $headerRes = $this->checkHeaderData($paramData['header']);
        if ($headerRes['status'] != STATUS_CODE_SUCCESS) {
            return $headerRes;
        }
        return funReturn(STATUS_CODE_SUCCESS, 'success', ['api_key' => $headerRes['data']['api_key'], 'data' => $paramData['body'] ?? []]);
    }


    /**
     * 校验头部数据
     * User:端木凌风
     * Date:2023-11-09 01:21
     * @param array $data
     * @return object|array|void|mixed
     */
    private function checkHeaderData(array $data = [])
    {

        $rule = [
            'api_key' => 'require|alphaNum',
            'sign' => "require|alphaNum",
            'request_time' => 'require|dateFormat:Y-m-d H:i:s'
        ];
        $message = [
            'api_key.require' => '缺少api_key参数',
            'api_key.alphaNum' => 'api_key参数格式错误',
            'sign.require' => '缺少sign参数',
            'sign.alphaNum' => 'sign参数格式错误',
            'request_time.require' => '缺少request_time参数',
            'request_time.dateFormat' => 'request_time参数格式错误',
        ];
        $validate = Validate::message($message)->rule($rule);

        if (!$validate->check($data)) {
            return funReturn(STATUS_CODE_DATAERROR, $validate->getError());
        }


        $clientInfo = Redis::get(config('common_data.cacheKey')['auth_client_detail'] . $data['api_key']);
        if ($clientInfo) {
            $clientInfo = json_decode($clientInfo, true);

        } else {
            $clientInfo = $this->clientRep->getInfoById($data['api_key']);
            if ($clientInfo) {
                Redis::set(config('common_data.cacheKey')['auth_client_detail'] . $data['api_key'], json_encode($clientInfo), 'EX', 3600 * 10);
            }
        }
        if (!$clientInfo) {
            return funReturn(STATUS_CODE_DATAERROR, 'api_key非法');
        }
        $viladSign = md5("api_key=" . $data['api_key'] . "&request_time=" . $data['request_time'] . "&api_secret=" . $clientInfo['secret']);
        if ($data['sign'] != $viladSign) {
            return funReturn(STATUS_CODE_DATAERROR, 'sign错误');
        }
        return funReturn(STATUS_CODE_SUCCESS, 'succes', $data);
    }



    /**
     * 响应后回调
     * User:端木凌风
     * Date:2023-11-09 01:11
     * @param $requestUrl
     * @param $result
     * @param $requestData
     * @return object|array|void|mixed
     */
    private function responseCallBack($requestUrl, $result, $requestData)
    {
        $result = is_string($result) ? json_decode($result, true) : $result;
        $requestData = is_string($requestData) ? json_decode($requestData, true) : $requestData;
        $clientId = $requestData['header']['api_key'] ?? 0;
        $responseCode = $result['status'] ?? '';
        /* if exist large size data mast reduce size*/
        if ($requestUrl == 'files_uploadFile' and is_array($requestData) and isset($requestData['body']['file'])) {
            if (is_string($requestData['body']['file'])) {
                $file = substr($requestData['body']['file'], 0, 20) . '....' . substr($requestData['body']['file'], -20);
                $requestData['body']['file'] = $file;
            }
        }
        $this->mqClient->sendMessage(MsgQueue::QUEUE_OUTAPI_ACCESSLOG, [
            'client_id' => $clientId,
            'request_time' => date('Y-m-d H:i:s'),
            'request_url' => $requestUrl,
            'data' => is_string($requestData) ? $requestData : json_encode($requestData, JSON_UNESCAPED_UNICODE),
            'request_type' => 'RPC',
            'response_code' => $responseCode,
            'response_data' => is_string($result) ? $result : json_encode($result, JSON_UNESCAPED_UNICODE)
        ]);
        $requestData = null;
        unset($requestData);
    }

    /**
     * 响应
     * User:端木凌风
     * Date:2023-11-09 01:48
     * @param $status
     * @param $msg
     * @param $data
     * @return object|array|void|mixed
     */
    protected function response($status, $msg = '', $data = [])
    {
        return json_encode(['status' => $status, 'msg' => $msg, 'data' => $data, 'time' => date('Y-m-d H:i:s')]);
    }

    /**
     * 加载路由
     * User:端木凌风
     * Date:2023-11-09 00:23
     * @return object|array|void|mixed
     */
    private function loadRoute()
    {
        $routes = [];
        foreach (self::$routeConfig as $namespace => $config) {
            if (!preg_match('/^([a-zA-Z])+[A-Za-z0-9\\\]+$/', (string)$namespace)) {
                $this->output->writeln("\033[33m命名空间格式不符\033[0m -- {$namespace}");
                exit;
            }
            $ds = $this->formatRouteGroup($config, '', 0);
            $routes[$namespace] = $ds;
        }
        self::$routes = $routes;

    }

    /**
     * 格式化路由
     * User:端木凌风
     * Date:2023-11-09 01:29
     * @param $routes
     * @param $prefix
     * @return object|array|void|mixed
     */
    private function formatRoutes($routes, string $prefix = '')
    {
        $dt = [];
        foreach ($routes as $route) {
            $dt[] = ['path' => $route['path'], 'prefix' => $prefix, 'name' => $route['name']];
        }
        return $dt;
    }

    /**
     * 格式化路由分组
     * User:端木凌风
     * Date:2023-11-09 01:09
     * @param $groups
     * @param $prefix
     * @param $lv
     * @return object|array|void|mixed
     */
    private function formatRouteGroup($groups, string $prefix = '', int $lv = 0)
    {
        static $routes = [];
        if ($lv == 0) $routes = [];
        $routeArr = $groups['routes'] ?? [];
        unset($groups['routes']);
        $dt = [];
        if ($routeArr) $dt = $this->formatRoutes($routeArr, $prefix);
        $routes = array_merge($routes, $dt);
        foreach ($groups as $px => $group) {
            $this->formatRouteGroup($group, $prefix . '_' . $px, 1);
        }
        return $routes;
    }

    /**
     * 解析地址
     * User:端木凌风
     * Date:2023-11-09 01:38
     * @param $namespace
     * @param string $controller
     * @return object|array|void|mixed
     */
    private function parsePath($namespace, string $controller): array
    {
        list($classAsStr, $method) = explode('@', $controller);
        $class = join('\\', array_filter([$namespace, $classAsStr]));
        $name = md5($class);
        if (!isset(self::$instance[$name])) {
            self::$instance[$name] = new $class();
        }
        return [self::$instance[$name], $method];
    }


    /**
     * 注册服务
     * User:端木凌风
     * Date:2023-11-09 01:51
     * @return object|array|void|mixed
     */
    protected function register()
    {
        $this->loadRoute();
        $uri = array_shift(self::$uris);

        if (!$uri) {
            $this->output->writeln(" - \033[未配置监听地址\033[0m");
            exit;
        }

        $server = new Server($uri);
        array_map(function ($uri) use ($server) {
            $server->addListener($uri);
        }, self::$uris);

        foreach (self::$routes as $namespace => $routes) {
            foreach ($routes as $route) {
                $name = ltrim(rtrim(trim($route['prefix'], '_') . '_' . trim($route['name'], '_'), '_'), '_');
                $this->registerMethods[] = $name;
                list($class, $method) = $this->parsePath($namespace, $route['path']);
                $server->addMethod($method, $class, $name);
            }
        }
        return $server;
    }


    private function outputInfo()
    {
        $hproseVersion = InstalledVersions::getVersion('hprose/hprose');

        $this->output->writeln("\033[33m版本：\033[0m");
        $this->output->writeln(" - ThinkPHP=\033[32m" . App::VERSION . "\033[0m");
        $this->output->writeln(" - Hprose-php=" . "\033[32m" . $hproseVersion . "\033[0m");
        $this->output->newLine();
        $this->output->writeln("\033[33m监听：\033[0m");

        foreach (config('hprose.uris') as $uri) {
            $this->output->writeln(" - \033[32m" . $uri . "\033[0m");
        }

        $this->output->newLine();
        $this->output->comment("\033[33m可调用远程方法:\033[0m");
        if ($this->registerMethods) {
            foreach ($this->registerMethods as $name) {
                $this->output->writeln(" - \033[32m" . $name . "\033[0m");
            }
        } else {
            $this->output->writeln(" - \033[32m无可调用方法\033[0m");
        }
    }
}
